#pragma once
#include "zMatrixBase.hpp"
#include <Math/Array2.hpp>

//matrix and operator + -

namespace zzz{
template<typename T, typename Major=zColMajor>
class zMatrix : public zMatrixBaseW<T,Major>
{
  using zMatrixBaseW<T,Major>::ToIndex;
  using zMatrixBaseW<T,Major>::rows_;
  using zMatrixBaseW<T,Major>::cols_;
public:
  //constructor
  zMatrix():zMatrixBaseW<T,Major>(0,0){}
  explicit zMatrix(zuint size):zMatrixBaseW<T,Major>(0,0) {
    SetSize(size);
  }

  zMatrix(zuint r, zuint c)
  :zMatrixBaseW<T,Major>(0,0) {
    SetSize(r,c);
  }

  zMatrix(const zMatrix<T> &other)
  :zMatrixBaseW<T,Major>(0,0) {
    *this=other;
  }

  explicit zMatrix(const zMatrixBaseR<T,Major> &other)
  :zMatrixBaseW<T,Major>(0,0) {
    *this=other;
  }
  
  const T* Data() const { return V_.Data();}
  T* Data() { return V_.Data();}
  /////////////////////////////////////////////////////////
  //operator()
  const T operator()(zuint r, zuint c) const {
    CheckRange(r, c);
    return V_[ToIndex(r,c)];
  }

  T& operator()(zuint r, zuint c) {
    CheckRange(r, c);
    return V_[ToIndex(r,c)];
  }

  const T operator[](zuint i) const {
    CheckRange(i);
    return V_[i];
  }

  T& operator[](zuint i) {
    CheckRange(i);
    return V_[i];
  }
  using zMatrixBaseW<T,Major>::operator();
  /////////////////////////////////////////////////////////
  //operator=
  template<typename T2>
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major>& other) {
    SetSize(other.rows_,other.cols_);
    for (zuint r=0; r<rows_; r++) for (zuint c=0; c<cols_; c++)
      (*this)(r,c)=static_cast<T2>(other(r,c));
    return *this;
  }
  const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major>& other) {
    SetSize(other.rows_,other.cols_);
    return zMatrixBaseW<T,Major>::operator =(other);
  }

  // Special for matrix of the same major, will call memcpy if available
  const zMatrixBaseW<T,Major>& operator=(const zMatrix<T,Major>& other) {
    SetSize(other.rows_,other.cols_);
    V_.SetData(other.V_.Data());
    return *this;
  }

  /////////////////////////////////////////////////////////
  //set size
  void SetSize(zuint size) {
    if (Major::Dim_==0) SetSize(1,size);
    else SetSize(size,1);
  }

  void SetSize(zuint r, zuint c) {
    V_.SetSize(Vector2ui(r,c));
    rows_=r;
    cols_=c;
  }

  //set zero
  void Zero(zuchar x=0) {
    V_.Zero(x);
  }

  void Fill(const T &a) {
    for (zuint j=0; j<cols_; j++) for (zuint i=0; i<rows_; i++)
      (*this)(i,j)=a;
  }

  void Fill(const zuint size, const T &a) {
    SetSize(size);
    Fill(a);
  }

  void Fill(const zuint r, zuint c, const T &a) {
    SetSize(r, c);
    Fill(a);
  }

private:
  Array<2,T> V_;
};


typedef zMatrix<double> zMatrixd;
typedef zMatrix<float> zMatrixf;
}

